JDBC教程:JDBC连接Mysql数据库超详细讲解(从入门到熟练使用) | 您所在的位置:网站首页 › db2 jdbc连接指定scheme › JDBC教程:JDBC连接Mysql数据库超详细讲解(从入门到熟练使用) |
JDBC
1.概念:
【 Java DataBase Connectivity 】,Java数据库连接即 ,JDBC的作用是:用Java语言操作数据库
2.本质:
官方定义的一套操作所有关系型数据库的规则/规范( 即接口-- API(Application Programming Interface )) 且由关系型数据库厂商自己写JDBC的实现类,实现这套接口(数据库驱动) 真正执行的代码是驱动jar包中的实现类 3.快速入门 步骤导入驱动jar包 图1--查看MySQL版本 图2---到官网下载对应版本的jar包https://downloads.mysql.com/archives/c-j/ 是旧版本 页面有所不同 MySQL :: Download Connector/J 附上最新版下载地址 图3---直接下载无需登录注册Oracle官网账号在src中新建libs包 复制mysql-connector-java-8.0.26.jar 到项目libs目录下 图4.图5.图6--复制jar包并粘贴右键—>add as library(添加为库) 图7--添加为库注册驱动 Class.forName("com.mysql.jdbc.Driver");注:需要抛出异常(如图) 使用throws Exception代替具体的异常类型(如throws ClassNotFoundException) 获取数据库连接对象 Connection create database JDBC_study; use JDBC_study; create table account ( id int auto_increment primary key , name varchar(10) null, balance double null ); 图8–提前准备的数据表 Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBC_study"); //JDBC_study是我的数据库名MySQL 8.0 以上版本的数据库连接: com.mysql.jdbc.Driver 更换为 com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/数据库名更换为jdbc:mysql://localhost:3306/数据库名?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC 定义sql语句 String sql="update account set balance =500 where id=1";获取执行sql语句的对象 statement Statement statement =connection.createStatement();执行sql,接收返回结果 int count=statement.executeUpdate(sql);处理结果 System.out.println(count);//释放资源 statement.close(); connection.close();完整代码: /* JDBC快速入门 */ public class jdbcDemo1 { public static void main(String[] args) throws Exception { // 1. 导入驱动jar包(已完成) // 2. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver");//可省略 // 3. 获取数据库连接对象 Connection //第二、三个参数是连接数据库的用户名和密码 Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC", "root", "Hky6600115"); // 4. 定义sql语句 String sql="update account set balance =500 where id=1"; // 5. 获取执行sql语句的对象 statement Statement statement =connection.createStatement(); // 6. 执行sql,接收返回结果 int count=statement.executeUpdate(sql); // 7. 处理结果 System.out.println(count); // 8. 释放资源 statement.close(); connection.close(); } }运行检验: 图9--控制台输出(暂时不解读) 图10--刷新数据库可见数据已修改 详解各对象 1️⃣.DriverManager 驱动管理对象【一个类】 功能: 一、注册驱动:告诉程序应当使用哪一个数据库去驱动jar包 static void registerDriver(Driver driver) DriverManager 的方法 上面快速入门的时候没有注册驱动 ,取而代之的是Class.forName(“com.mysql.jdbc.Driver”)也成功运行,原因如下: 查看mysql.jdbc.Driver的源码可知 //这是com.mysql.jdbc.Driver的静态代码块,只要使用这个类,就会执行这段代码 //而Class.forName("com.mysql.jdbc.Driver")就正好使用到了这个类 static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }因此,我们是通过给forName指定了MySQL的驱动,它帮助我们注册驱动 注意:MySQL5.0之后可以省略注册驱动的步骤 在jar包中,存在一个java.sql.Driver配置文件,文件中指定了com.mysql.jdbc.Driver 二、获取数据库连接 方法:static Connection getConnection(String url, String user, String password); 带三个参数 参数: url:指定连接的路径 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称【MySQL5.0写法】 语法:jdbc:mysql://ip地址(域名):端口号/JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC【MySQL8.0写法】 ip地址(域名)可以找到某一台计算机 端口号可以找到计算机上安装的MySQL服务器 细节:若连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为jdbc:mysql:///数据库名称【MySQL5.0写法】,即省略 ip地址(域名):端口号 user:用户名(本机常为root) password:密码 2️⃣. Connection 数据库连接对象功能: 一、获取执行sql的对象 获取普通执行者对象:Statement createStatement();获取预编译执行者对象:PreparedStatement prepareStatement(String sql);二、管理事务 业务操作的多个步骤:要么同时成功,要么同时失败 数据库可以:开启/提交/回滚事务,针对此有以下三个对应方法: 开启事务:setAutoCommit(boolean autoCommit); 参数为false,则开启事务提交事务:commit();回滚事务:rollback(); 3️⃣.statement 执行sql的对象⭐功能: 执行sql对象 boolean execute(String sql); 可以执行任意sql语句(了解即可) int executeUpdate(String sql); 执行DML(insert、update、delete)语句以及DDL(create、alert、drop) 返回值:影响的行数 eg. 前面运行过的,原先的表格经修改,id=1的balance由1000变成500,则这一行数据受到影响被改变返回值为1(见图9) idnamebalance1zhangsan1000---->5002lisi1000作用:可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功;反之,则失败 ResultSet executeQuery(String sql); 执行DQL(select) 练习:基础1.accunt表 添加一条记录 2.account表 修改记录 3.account表 删除一条记录 import java.sql.*; /* 1.accunt表 添加一条记录 */ public class jdbcDemo2 { public static void main(String[] args) throws Exception{ Statement statement=null; Connection connection=null; try { //1.注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2.定义sql String sql="insert into account values(null,'wangwu',3000)"; //3.获取connection对象 connection= DriverManager.getConnection("jdbc:mysql:///JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC", "root", "Hky6600115"); //4.获取执行sql的对象statement statement=connection.createStatement(); //5.执行sql int count=statement.executeUpdate(sql);//影响行数【增加一行,应返回1】 //6.处理结果 System.out.println(count); if (count>0){ System.out.println("执行成功"); }else { System.out.println("执行失败"); } }catch (ClassNotFoundException e){ e.printStackTrace(); }catch (SQLException e){ e.printStackTrace(); /* 释放但因作用域问题报错-->因此提升作用域 statement.close(); connection.close(); */ }finally { //7.释放资源 //避免空指针异常 if(statement!=null){ try { statement.close(); }catch (SQLException e){ e.printStackTrace(); } } if(connection!=null){ try { connection.close(); }catch (SQLException e){ e.printStackTrace(); } } } } }请仿造以上练习1的代码 ,完成练习2以及练习3 4️⃣.ResultSet 结果集对象 boolean next() 如果有数据返回true,且光标向下移动一行没有则返回false XXX getXxx(列名/列编号) XXX 表示数据类型 eg.int、String例如:int getInt(int)可以有 int getInt(1)或者int getInt("id")代码示例: import java.sql.*; public class jdbcDemo5 { public static void main(String[] args) { Connection connection=null; Statement statement=null; ResultSet resultSet=null; try { Class.forName("com.mysql.cj.jdbc.Driver"); String sql="select * from account"; connection= DriverManager.getConnection("jdbc:mysql:///JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC","root","Hky6600115"); statement=connection.createStatement(); resultSet= statement.executeQuery(sql); //处理结果 //1.让光标向下移动一行(默认在列名一行) resultSet.next(); //2.获取数据 int id=resultSet.getInt("id"); String name=resultSet.getString("name"); double balance=resultSet.getDouble("balance"); //3.打印数据 System.out.println(id+"---"+name+"---"+balance); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if(statement!=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } 图--运行结果 练习:遍历案例代码: 其他步骤不变,仅改变数据处理过程 //处理结果(输出全部数据) //1.判断游标是否为最后一行末尾 while (resultSet.next()){ //2.获取数据 int id=resultSet.getInt("id"); String name=resultSet.getString("name"); double balance=resultSet.getDouble("balance"); //3.打印数据 System.out.println(id+"---"+name+"---"+balance); } 练习:封装对象数据准备: insert into emp VALUES (1001,'孙悟空',4,1004,'2000-12-17','8000.00',NULL,20), (1002,'卢俊义',3,1006,'2001-02-20','16000.00','3000.00',30), (100,'林冲',3,1006,'2001-02-22','12500.00','5000.00',30), (1004,'唐僧',2,1009,'2001-04-02','29750.00',NULL,20), (1005,'李逵',4,1006,'2001-09-28','12500.00','14000.00',30), (1006,'宋江',2,1009, '2001-05-01','28500.00',NULL,30), (1007,'刘备',2,1009,'2001-09-01','24500.00',NULL,10), (1008,'猪八戒',4,1004,'2007-04-19','30000.00',NULL,20), (1009,'罗贯中',1,NULL,'2001-11-17','50000.00',NULL,10), (1010,'吴用',3,1006,'2001-09-08','15000.00','0.00',30), (1011,'沙僧',4,1004,'2007-05-23','11000.00',NULL,20), (1012,'李逵',4,1006,'2001-12-03','9500.00',NULL,30), (1013,'小白龙',4,1004,'2001-12-03','30000.00',NULL,20), (1014,'关羽',4,1007,'2002-01-23','13000.00',NULL,10);要求:定义一个方法,查询emp表中的数据并将其封装为对象,然后装载集合,返回 步骤: 定义emp类定义方法 public List< EMP > findAll(){}实现方法 select*from emp;代码实现: 配置文件jdbc.properties url="jdbc:mysql:///JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC" user=root password=Hky6600115' driver=com.mysql.cj.jdbc.Driver JDBCUtils工具类 public class JDBCUtils{ private static String url; private static String user; private static } 抽取JDBC工具类(JDBCUtils)目的:简化书写 分析: 注册驱动 连接对象 需求: 传递参数❌(太麻烦) 保障工具类的通用性(MySQL5.0/MySQL8.0的url不同) 解决:配置文件jdbc.properties【在src下创建】 url=“” user=“” password=“” driver=“” 实现代码: import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.sql.*; import java.util.Properties; /* JDBC工具类 */ public class JDBCUtils { /* 文件的读取只需要一次,便可以得到所有值 利用【静态代码块】 */ private static String url; private static String user; private static String password; private static String driver; static { //读取资源文件,获取值 try { //1.创建properties集合类 Properties properties=new Properties(); //获取src路径下的文件的方式--->classLoader 类加载器 ClassLoader classLoader = JDBCUtils.class.getClassLoader(); URL resource = classLoader.getResource("jdbc.properties"); String path = resource.getPath(); //System.out.println(path); //2.加载文件 // properties.load(new FileReader("jdbc.properties")); properties.load(new FileReader(path)); //3.获取数据,赋值 url=properties.getProperty("url"); user=properties.getProperty("user"); password=properties.getProperty("password"); driver=properties.getProperty("driver"); //4.注册驱动 Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } // 释放资源 public static void close(ResultSet resultSet, Statement statement, Connection connection){ if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if(statement!=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void close(PreparedStatement preparedStatement, Connection connection) { if(preparedStatement!=null){ try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } 工具类练习需求: 通过键盘录入用户名和密码判断用户是否登录成功 select * from user where username=““and password =””;如果该sql语句有查询结果,则表示成功,反之则失败步骤: 创建数据表user create table user( id int primary key auto_increment, username varchar(32), password varchar(32) ); insert into user values (null,'zhangsan','123'); insert into user values (null,'lisi','234');编写登录方法 代码实现: /* 登录方法 */ public boolean logion(String username,String password) { if(username==null||password==null){ return false; } //连接数据库判断是否登录成功 Connection connection=null; Statement statement=null; ResultSet resultSet=null; try { connection= JDBCUtils.getConnection(); String sql="select * from user where username='"+username+"'and password='"+password+"' "; statement=connection.createStatement(); resultSet=statement.executeQuery(sql); return resultSet.next(); } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils.close(resultSet,statement,connection); } return false; } 5️⃣.PreparedStatement 执行sql的对象sql注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接,会造成安全性问题 以上面的练习为例 输入用户名:随机(eg.wangwu) 输入密码:a ’ or ’ a ’ = ’ a sql语句:select * from user where username =‘wangwu’ and password='a’or’a’=‘a’;原sql语句:“select * from user where username='”+username+“‘and password=’”+password+"’ "仔细对比观察 经典例子: 漫画中,该学生的姓名为“Robert’); DROP TABLE students;–”,导致students表被删除: 解决:使用PreparedStatement 对象 预编译sql:参数使用?作为占用符 步骤(稍复杂): 导入驱动jar包注册驱动获取数据库连接对象 connection定义sql sql的参数使用?作为占位符如:select * from user where username = ? and password = ?; 获取执行sgl语句的对象 PreparedStatement Connection.PreparedStatement (String sql) 需要传参 给?赋值: 方法:setXxx(参数1,参数2)参数1:?的位置参数2:?的值 执行sql,接受返回结果 不需要传参处理结果释放资源代码修改: /* 登录方法,使用PreparedStatement 对象 */ public boolean logion(String username,String password) { if(username==null||password==null){ return false; } //连接数据库判断是否登录成功 Connection connection=null; ResultSet resultSet=null; PreparedStatement preparedStatement=null; try { connection= JDBCUtils.getConnection(); String sql="select * from user where username=?and password=?"; PreparedStatement=connection.preparedStatement(sql); preparedStatement.setString(1,username); preparedStatement.setString(2,password); resultSet=preparedStatement.executeQuery(); return resultSet.next(); } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils.close(resultSet,preparedStatement,connection); } return false; } 注意:后期改用 preparedStatement 来完成增删改查的操作 优势: 防止sql注入效率更高 JDBC控制事务事务 :一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。 操作: 开启事务提交事务回滚事务使用connection对象来管理事务 开启事务 setAutoCommit(boolean autoCommit) : 调用该方法设置参数为 false,即开启事务 在执行sql之前开启事务 提交事务 commit() 当所有sql都执行完提交事务 回滚事务 rollback() 在catch中回滚事务 练习:转账代码实现: import cn.itcast.util.JDBCUtils; import java.sql.*; /* 事务操作 */ public class jdbcDemo10 { /* 转账方法 */ public static void main(String[] args) throws Exception { Connection connection=null; PreparedStatement preparedStatement1=null; PreparedStatement preparedStatement2=null; ResultSet resultSet=null; try { //1.获取连接 connection= JDBCUtils.getConnection(); // 开启事务 connection.setAutoCommit(false); //2.定义sql //2.1. 张三-500 //2.2. 李四+500 String sql1="update account set balance = balance - ? where id = ?"; String sql2="update account set balance = balance + ? where id = ?"; //3.获取执行对象 preparedStatement1=connection.prepareStatement(sql1); preparedStatement2=connection.prepareStatement(sql2); //4.设置参数 preparedStatement1.setDouble(1,500); preparedStatement1.setInt(2,1); preparedStatement2.setDouble(1,500); preparedStatement2.setInt(2,2); //5.执行sql preparedStatement1.executeUpdate(); preparedStatement2.executeUpdate(); // 提交事务 connection.commit(); } catch (Exception e) { // 事务回滚 try { if(connection!=null){ connection.rollback(); } }catch (SQLException e1){ e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtils.close(preparedStatement1,connection); JDBCUtils.close(preparedStatement2,null); } } } 数据库与连接池 1. 概念:存放数据库连接的一个容器(集合)Connection 当系统运行起来之后,这个连接池就被创建,在这个连接池当中,会申请一些对象,当有用户来访问数据库的时候,就从这个连接池当中获取连接对象,用户访问结束之后,连接池对象会归还给容器 2.优点: 节约资源用户访问高效 3. 连接池实现: java官方提供的数据库连接池规范(接口):DataSource (javax.sql包下) 方法:【获取连接:getConnection();】【归还连接池对象:conn.close();】 由数据库厂商为我们实现该接口 C3P0Druid (阿里巴巴开发) 4.C3P0 步骤: 1. 导入jar包官网:https://sourceforge.net/ 进入官网,搜索框中输入c3p0,并点击下面的提示 点击下载即可 解压后,在lib文件夹中,复制如下两个jar包到项目lib文件夹中(还有不能忘记数据库驱动jar包mysql-connector-java-8.0.26.jar) 右键libs添加为库 2. 定义配置文件: 文件命名为:c3p0.properties 或者 c3p0-config.xml 位置:src目录下1.自定义的c3p0-config.xml com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/jdbc_study root Hky6600115 5 10 3000 com.mysql.jdbc.Driver jdbc:mysql://localhost:3306/day25 root root 5 8 1000 2.参数解释 initialPoolSize:初始化申请的连接数量maxPoolSize:最大的连接数量checkoutTimeout:超时时间 3. 创建核心对象数据库连接池对象 ComboPooledDataSource 4. 获取连接getConnection 代码实现: import com.mchange.v2.c3p0.ComboPooledDataSource; import javax.sql.DataSource; import java.sql.Connection; /* c3p0的演示 */ public class c3p0Demo1 { public static void main(String[] args) throws Exception { //1.创建数据库连接池对象 DataSource dataSource=new ComboPooledDataSource(); //2.获取连接对象 Connection connection = dataSource.getConnection(); //3.打印 System.out.println(connection); } } 参数检验:5 10 3000 1. 检验最大连接数量 public class c3p0Demo2 { public static void main(String[] args) throws Exception { DataSource dataSource=new ComboPooledDataSource(); for (int i=1;i Connection connection=dataSource.getConnection(); System.out.println(i+":"+connection); }运行结果:(先输出正常部分,隔3秒报错) 若中途归还对象,即可正常(重复使用) public static void main(String[] args) throws SQLException { DataSource dataSource=new ComboPooledDataSource(); for (int i=1;i connection.close(); } } } 注: DataSource dataSource=new ComboPooledDataSource();表示使用默认配置DataSource dataSource=new ComboPooledDataSource("otherc3p0");表示使用指定名称配置 5.Druid 步骤: 1. 导入jar包官网下载地址:Central Repository: com/alibaba/druid (maven.org) (详见c3p0) 2.定义配置文件 是properties形式的可以叫任意名称,可以放在任意目录下 不会自动加载,需要手动 //示例 druid.properties driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql:///JDBC_study?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC username=root password=password # 初始化连接数量 initialSize=5 #最大连接数 maxActive=10 # 最大等待时间 maxWait=3000步骤+1:加载配置文件 3. 获取数据库连接池对象通过工厂类来获取 DruidDataSourceFactory 4. 获取连接getConnection 代码实现: import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; public class DruidDemo { public static void main(String[] args) throws Exception { //加载配置文件 Properties properties=new Properties(); InputStream inputStream=DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); properties.load(inputStream); //获取连接池对象 DataSource dataSource =DruidDataSourceFactory.createDataSource(properties); //获取连接 Connection connection=dataSource.getConnection(); System.out.println(connection); } } 定义工具类 1. 定义一个类 2. 静态代码块 提供静态代码块加载配置文件,初始化连接池对象 3. 提供方法 获取连接方法;通过数据库连接池获取连接释放资源获取连接池的方法 代码实现: import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /* Druid连接池工具类 */ public class JDBCUtils { private static DataSource dataSource; static { try { Properties properties=new Properties(); properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource= DruidDataSourceFactory.createDataSource(properties); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /* 获取连接的方法 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /* 释放资源 */ public static void close(ResultSet resultSet,Statement statement, Connection connection){ if (resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement!=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection!=null){ try { connection.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } public static void close(Statement statement, Connection connection){ close(null,statement,connection); } /* 获取连接池 */ public static DataSource getDataSource(){ return dataSource; } } JDBCTemplateSpring框架对JDBC的简单封装,提供了一个JdbcTemplate对象,大大的简化了开发 步骤 1. 导入jar包以下是我的下载参考页面地址↓ spring jar包 以及 jdbcTemplate 相关jar包下载_jdbctemplate的jar包-CSDN博客 2.创建JdbcTemplate对象 依赖于数据源DataSourceJdbcTemplate template = new jdbcTemplate(dataSource); import cn.itcast.JDBC数据库与连接池.datasource.utils.JDBCUtils; import org.springframework.jdbc.core.JdbcTemplate; /* JDBCTemplateDemo1 入门 */ public class JDBCTemplateDemo1 { public static void main(String[] args) { // 1.导入jar包 // 2.创建JDBCTemplate JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource()); // 3.调用方法 String sql="update account set balance=5000 where id=?"; int count =template.update(sql,2); System.out.println(count); } } 3. 调用JdbcTemplate的方法来完成CRUD的操作 update():执行DML语句。增、删、改语句queryForMap():查询结果将结果集封装为map集合queryForList():查询结果将结果集封装为list集合query():查询结果,将结果封装为JavaBean对象queryForobject:查询结果,将结果封装为对象 4. 练习 修改1号数据的 salary 为 10000添加一条记录删除刚才添加的记录查询id为1的记录,将其封装为Map集合查询所有记录,将其封装为List查询所有记录,将其封装为Emp对象的List集合查询总记录数 |
CopyRight 2018-2019 实验室设备网 版权所有 |